import torch
import torch.nn as nn
import torch.nn.functional as F

class FlowHead(nn.Module):
    def __init__(self, input_dim=128, hidden_dim=256, output_dim=2):
        super(FlowHead, self).__init__()
        self.conv1 = nn.Conv2d(input_dim, hidden_dim, 3, padding=1)
        self.conv2 = nn.Conv2d(hidden_dim, output_dim, 3, padding=1)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        return self.conv2(self.relu(self.conv1(x)))

# 用于获得残差视差
class DispHead(nn.Module):
    def __init__(self, input_dim=128, hidden_dim=256, output_dim=1):
        super(DispHead, self).__init__()
        self.conv1 = nn.Conv2d(input_dim, hidden_dim, 3, padding=1) # 输入128通道，输出256通道，尺寸不变
        self.conv2 = nn.Conv2d(hidden_dim, output_dim, 3, padding=1) # 输入256通道，输出1通道，尺寸不变
        self.relu = nn.ReLU(inplace=True) # ReLU

    def forward(self, x):
        return self.conv2(self.relu(self.conv1(x))) # 输入128通道，输出1通道，尺寸不变

# 卷积门控循环单元
class ConvGRU(nn.Module):
    def __init__(self, hidden_dim, input_dim, kernel_size=3):
        super(ConvGRU, self).__init__()
        self.convz = nn.Conv2d(hidden_dim+input_dim, hidden_dim, kernel_size, padding=kernel_size//2) # 输入(隐藏状态+输入维度)通道，输出隐藏状态通道，尺寸不变
        self.convr = nn.Conv2d(hidden_dim+input_dim, hidden_dim, kernel_size, padding=kernel_size//2)
        self.convq = nn.Conv2d(hidden_dim+input_dim, hidden_dim, kernel_size, padding=kernel_size//2)

    def forward(self, h, cz, cr, cq, *x_list): # 输入上个隐藏状态(h)，上下文特征(cz)，上下文特征(cr)，上下文特征(cq)，输入张量列表(*x_list)
        x = torch.cat(x_list, dim=1) # 输入张量
        hx = torch.cat([h, x], dim=1) # 连接上个隐藏状态和输入张量
        z = torch.sigmoid(self.convz(hx) + cz) # 更新门，sigmoid(上个隐藏状态和输入的连接张量+上下文特征)
        r = torch.sigmoid(self.convr(hx) + cr) # 重置门，sigmoid(上个隐藏状态和输入的连接张量+上下文特征)
        q = torch.tanh(self.convq(torch.cat([r*h, x], dim=1)) + cq) # tanh激活，tanh(连接重置门*上个隐藏状态和输入张量+上下文特征)
        h = (1-z) * h + z * q # (1-更新门)*上个隐藏状态+更新门*tanh结果
        return h # 输出当前隐藏状态(h)

class SepConvGRU(nn.Module):
    def __init__(self, hidden_dim=128, input_dim=192+128):
        super(SepConvGRU, self).__init__()
        self.convz1 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (1,5), padding=(0,2))
        self.convr1 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (1,5), padding=(0,2))
        self.convq1 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (1,5), padding=(0,2))

        self.convz2 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (5,1), padding=(2,0))
        self.convr2 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (5,1), padding=(2,0))
        self.convq2 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (5,1), padding=(2,0))

    def forward(self, h, *x):
        # horizontal
        x = torch.cat(x, dim=1)
        hx = torch.cat([h, x], dim=1)
        z = torch.sigmoid(self.convz1(hx))
        r = torch.sigmoid(self.convr1(hx))
        q = torch.tanh(self.convq1(torch.cat([r*h, x], dim=1)))
        h = (1-z) * h + z * q

        # vertical
        hx = torch.cat([h, x], dim=1)
        z = torch.sigmoid(self.convz2(hx))
        r = torch.sigmoid(self.convr2(hx))
        q = torch.tanh(self.convq2(torch.cat([r*h, x], dim=1)))
        h = (1-z) * h + z * q

        return h

# 基本动作编码器
class BasicMotionEncoder(nn.Module):
    def __init__(self, args):
        super(BasicMotionEncoder, self).__init__()
        self.args = args
        cor_planes = args.corr_levels * (2*args.corr_radius + 1) * (8+1) # 组合几何编码体积的通道数=相关性金字塔层数*((2*相关性金字塔宽度)(组数)+1)*9=162
        self.convc1 = nn.Conv2d(cor_planes, 64, 1, padding=0) # 输入162通道，输出64通道，尺寸不变
        self.convc2 = nn.Conv2d(64, 64, 3, padding=1) # 通道不变，尺寸不变
        self.convd1 = nn.Conv2d(1, 64, 7, padding=3) # 输入1通道，输出64通道，尺寸不变，卷积核7*7
        self.convd2 = nn.Conv2d(64, 64, 3, padding=1) # 通道不变，尺寸不变
        self.conv = nn.Conv2d(64+64, 128-1, 3, padding=1) # 输入64+64，输出127，尺寸不变

    def forward(self, disp, corr): # 输入回归视差(B,1,H,W)，组合几何编码体积(B,162,H,W)
        cor = F.relu(self.convc1(corr)) # 组合几何编码体积，输入162通道，输出64通道，尺寸不变
        cor = F.relu(self.convc2(cor)) # 通道不变，尺寸不变
        disp_ = F.relu(self.convd1(disp)) # 回归视差，输入1通道，输出64通道，尺寸不变，卷积核7*7
        disp_ = F.relu(self.convd2(disp_)) # 通道不变，尺寸不变

        cor_disp = torch.cat([cor, disp_], dim=1) # 在通道维度连接张量，输入改变通道后组合几何编码体积，改变通道后回归视差，输出(B,128,H,W)
        out = F.relu(self.conv(cor_disp)) # 输入128，输出127，尺寸不变
        return torch.cat([out, disp], dim=1) # 在通道维度连接张量，输入改变通道后(组合几何编码体积+回归视差)，回归视差，输出(B,128,H,W)

# 平均池化1/2
def pool2x(x):
    return F.avg_pool2d(x, 3, stride=2, padding=1) # 2d平均池化，尺寸/2

# 平均池化1/4
def pool4x(x):
    return F.avg_pool2d(x, 5, stride=4, padding=1) # 2d平均池化，尺寸/4

# 双线性插值
def interp(x, dest):
    interp_args = {'mode': 'bilinear', 'align_corners': True}
    return F.interpolate(x, dest.shape[2:], **interp_args) # 双线性插值x为dest.shape[2:]相同形状

# 多尺度GRU更新块
class BasicMultiUpdateBlock(nn.Module):
    def __init__(self, args, hidden_dims=[]): # 输入GRU隐藏状态的维度；默认[128,128,128]
        super().__init__()
        self.args = args
        self.encoder = BasicMotionEncoder(args) # 基本动作编码器
        encoder_output_dim = 128 # 编码器输出维度

        self.gru04 = ConvGRU(hidden_dims[2], encoder_output_dim + hidden_dims[1] * (args.n_gru_layers > 1)) # ConvGRU，输入GRU隐藏状态维度，输入维度(编码器输出维度，若GRU层数>1则+隐藏状态维度)；默认128，256
        self.gru08 = ConvGRU(hidden_dims[1], hidden_dims[0] * (args.n_gru_layers == 3) + hidden_dims[2]) # ConvGRU，默认128，256
        self.gru16 = ConvGRU(hidden_dims[0], hidden_dims[1]) # ConvGRU，默认128，128
        self.disp_head = DispHead(hidden_dims[2], hidden_dim=256, output_dim=1)
        factor = 2**self.args.n_downsample

        self.mask_feat_4 = nn.Sequential(
            nn.Conv2d(hidden_dims[2], 32, 3, padding=1), # 输入128层，输出32层，尺寸不变
            nn.ReLU(inplace=True)) # ReLU

    def forward(self, net, inp, corr=None, disp=None, iter04=True, iter08=True, iter16=True, update=True):
        if iter16: # True
            net[2] = self.gru16(net[2], *(inp[2]), pool2x(net[1])) # ConvGRU，输入上下文特征[2](tanh)->h；解包上下文特征[2](ReLU)List得到3个张量->cz,cr,cq；平均池化1/2上下文特征[1](tanh)->*x_list(*可以接受多个参数)；输出当前隐藏状态
        if iter08: # True
            if self.args.n_gru_layers > 2: # GRU的层数；默认3，True
                net[1] = self.gru08(net[1], *(inp[1]), pool2x(net[0]), interp(net[2], net[1])) # ConvGRU，输入上下文特征[1](tanh)->h；解包上下文特征[1](ReLU)List得到3个张量->cz,cr,cq；平均池化1/2上下文特征[0](tanh)，双线性插值上下文特征[2](tanh)->*x_list(*可以接受多个参数)；输出当前隐藏状态
            else:
                net[1] = self.gru08(net[1], *(inp[1]), pool2x(net[0])) # ConvGRU，输入上下文特征[1](tanh)->h；解包上下文特征[1](ReLU)List得到3个张量->cz,cr,cq；平均池化1/2上下文特征[0](tanh)->*x_list(*可以接受多个参数)；输出当前隐藏状态
        if iter04: # True
            motion_features = self.encoder(disp, corr) # 动作特征，输入回归视差(B,1,H,W)，组合几何编码体积(B,162,H,W)，连接改变通道后(组合几何编码体积+回归视差)+回归视差，输出(B,128,H,W)
            if self.args.n_gru_layers > 1: # GRU的层数；默认3，True
                net[0] = self.gru04(net[0], *(inp[0]), motion_features, interp(net[1], net[0])) # ConvGRU，输入上下文特征[0](tanh)->h；解包上下文特征[0](ReLU)List得到3个张量->cz,cr,cq；动作特征，双线性插值上下文特征[1](tanh)->*x_list(*可以接受多个参数)；输出当前隐藏状态
            else:
                net[0] = self.gru04(net[0], *(inp[0]), motion_features) # ConvGRU，输入上下文特征[0](tanh)->h；解包上下文特征[0](ReLU)List得到3个张量->cz,cr,cq；动作特征->*x_list(*可以接受多个参数)；输出当前隐藏状态

        if not update: # 不运行
            return net

        delta_disp = self.disp_head(net[0]) # 偏移视差，输入128通道，输出1通道，尺寸不变
        mask_feat_4 = self.mask_feat_4(net[0]) # 特征掩码，输入128通道，输出32通道，尺寸不变
        return net, mask_feat_4, delta_disp # 返回当前隐藏状态List，特征掩码，偏移视差
